package cn.xf.web.advice;

import cn.xf.common.base.ResEnv;
import cn.xf.common.exception.AppException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

@Slf4j
@Configuration
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@RestControllerAdvice
public class ExceptionAdvice {

    @Value("${spring.application.name}")
    private String serverIdName;
    @Value("${server.port}")
    private String serverPort;

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResEnv handleError(HttpServletRequest request, MissingServletRequestParameterException e) {
        log.warn("缺少请求参数", e.getMessage());
        String message = String.format("缺少必要的请求参数: %s", e.getParameterName());
        sendExMsg(request, e);
        return ResEnv.fail(HttpStatus.BAD_REQUEST.value(), message);
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResEnv handleError(HttpServletRequest request, MethodArgumentTypeMismatchException e) {
        log.warn("请求参数格式错误", e.getMessage());
        String message = String.format("请求参数格式错误: %s", e.getName());
        sendExMsg(request, e);
        return ResEnv.fail(HttpStatus.BAD_REQUEST.value(), message);
    }

    private void sendExMsg(HttpServletRequest request, Throwable ex) {

        // 采用全局捕获异常 拦截系统中的错误，返回友好的提示给客户端
        Map<String, String[]> parameterMap = request.getParameterMap();
        StackTraceElement[] stackTrace = ex.getStackTrace();
        StackTraceElement stackTraceElement = stackTrace[0];
        // 转换成json
        JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(stackTraceElement));
        // 新增参数内容
        jsonObject.put("parameterContent", parameterMap);
        // 新增错误日志
        jsonObject.put("errorContent", ex.getMessage());
        jsonObject.put("serviceId", serverIdName);
        jsonObject.put("createTime", new Date());
        jsonObject.put("messageId",  UUID.randomUUID().toString().replace("-",""));
        // 获取我们的ip
        jsonObject.put("serverIp", getServerAddres());
        log.info(jsonObject.toJSONString());
    }
    private String getServerAddres() {

        try {
            InetAddress ip4 = Inet4Address.getLocalHost();
            return ip4.getHostAddress() + ":" + serverPort;
        } catch (UnknownHostException e) {

            log.error("here is an exception", e);
            return null;
        }
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResEnv handleError(HttpServletRequest request, MethodArgumentNotValidException e) {
        log.warn("参数验证失败", e.getMessage());
        sendExMsg(request, e);
        return handleError(e.getBindingResult());
    }

    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResEnv handleError(HttpServletRequest request, BindException e) {
        log.warn("参数绑定失败", e.getMessage());
        sendExMsg(request, e);
        return handleError(e.getBindingResult());
    }

    private ResEnv handleError(BindingResult result) {
        FieldError error = result.getFieldError();
        String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
        return ResEnv.fail(HttpStatus.BAD_REQUEST.value(), message);
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResEnv handleError(HttpServletRequest request, NoHandlerFoundException e) {
        log.error("404没找到请求:{}", e.getMessage());
        //sendExMsg(request, e);
        return ResEnv.fail(HttpStatus.NOT_FOUND.value(), e.getMessage());
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResEnv handleError(HttpServletRequest request, HttpMessageNotReadableException e) {
        log.error("消息不能读取:{}", e.getMessage());
        sendExMsg(request, e);
        return ResEnv.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public ResEnv handleError(HttpServletRequest request, HttpRequestMethodNotSupportedException e) {
        log.error("不支持当前请求方法:{}", e.getMessage());
        sendExMsg(request, e);
        return ResEnv.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public ResEnv handleError(HttpServletRequest request, HttpMediaTypeNotSupportedException e) {
        log.error("不支持当前媒体类型:{}", e.getMessage());
        //sendExMsg(request, e);
        return ResEnv.fail(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), e.getMessage());
    }

    @ExceptionHandler(AppException.class)
    public ResEnv handleBrError(HttpServletRequest request, AppException e) {
        log.error("业务异常", e);
        sendExMsg(request, e);
        return ResEnv.fail(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResEnv handleError(HttpServletRequest request, Throwable e) {
        log.error("服务器异常", e);
        //发送服务异常事件
        sendExMsg(request, e);
        return ResEnv.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), (StringUtils.isEmpty(e.getMessage()) ? HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() : e.getMessage()));
    }
}
